package link.luyu.plugin.brochain.crypto;

import java.math.BigInteger;

public class SM2Signature {

    private final byte v;
    private final BigInteger r;
    private final BigInteger s;

    public SM2Signature(byte v, BigInteger r, BigInteger s) {
        this.v = v;
        this.r = r;
        this.s = s;
    }

    public byte getV() {
        return v;
    }

    public BigInteger getR() {
        return r;
    }

    public BigInteger getS() {
        return s;
    }

    /**
     * @return true if the S component is "low", that means it is below {@link
     *     SM2#HALF_CURVE_ORDER}. See <a
     *     href="https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki#Low_S_values_in_signatures">
     *     BIP62</a>.
     */
    public boolean isCanonical() {
        return s.compareTo(SM2.HALF_CURVE_ORDER) <= 0;
    }

    /**
     * Will automatically adjust the S component to be less than or equal to half the curve order,
     * if necessary. This is required because for every signature (r,s) the signature (r, -s (mod
     * N)) is a valid signature of the same message. However, we dislike the ability to modify the
     * bits of a Bitcoin transaction after it's been signed, as that violates various assumed
     * invariants. Thus in future only one of those forms will be considered legal and the other
     * will be banned.
     *
     * @return the signature in a canonicalised form.
     */
    public SM2Signature toCanonicalised() {
        if (!isCanonical()) {
            // The order of the curve is the number of valid points that exist on that curve.
            // If S is in the upper half of the number of valid points, then bring it back to
            // the lower half. Otherwise, imagine that
            //    N = 10
            //    s = 8, so (-8 % 10 == 2) thus both (r, 8) and (r, 2) are valid solutions.
            //    10 - 8 == 2, giving us always the latter solution, which is canonical.
            return new SM2Signature(v, r, SM2.CURVE.getN().subtract(s));
        } else {
            return this;
        }
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        SM2Signature that = (SM2Signature) o;

        if (v != that.v) {
            return false;
        }
        if (!r.equals(that.r)) {
            return false;
        }
        return s.equals(that.s);
    }

    @Override
    public int hashCode() {
        int result = v;
        result = 31 * result + r.hashCode();
        result = 31 * result + s.hashCode();
        return result;
    }
}
